forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import * as v from 'valibot'
2import { PackageRouteParamsSchema } from '#shared/schemas/package'
3import type { NpmVersionDist } from '#shared/types'
4import { CACHE_MAX_AGE_ONE_HOUR, ERROR_PROVENANCE_FETCH_FAILED } from '#shared/utils/constants'
5import {
6 parseAttestationToProvenanceDetails,
7 type NpmAttestationsResponse,
8} from '#server/utils/provenance'
9
10/**
11 * GET /api/registry/provenance/:name/v/:version
12 *
13 * Returns parsed provenance details for a package version (build summary, source commit, build file, public ledger).
14 * Version is required. Returns null when the version has no attestations or parsing fails.
15 */
16export default defineCachedEventHandler(
17 async event => {
18 const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
19
20 const { rawPackageName, rawVersion } = parsePackageParams(pkgParamSegments)
21
22 if (!rawVersion) {
23 throw createError({
24 statusCode: 400,
25 message: 'Version is required for provenance.',
26 })
27 }
28
29 try {
30 const parsed = v.parse(PackageRouteParamsSchema, {
31 packageName: rawPackageName,
32 version: rawVersion,
33 }) as { packageName: string; version: string }
34 const { packageName, version } = parsed
35
36 const packument = await fetchNpmPackage(packageName)
37 const versionData = packument.versions[version]
38 if (!versionData) {
39 throw createError({
40 statusCode: 404,
41 message: `Version ${version} not found for package ${packageName}.`,
42 })
43 }
44 const dist = versionData.dist as NpmVersionDist | undefined
45 const attestationsUrl = dist?.attestations?.url
46
47 if (!attestationsUrl) {
48 return null
49 }
50
51 const response = await $fetch<NpmAttestationsResponse>(attestationsUrl)
52 const details = parseAttestationToProvenanceDetails(response)
53 return details
54 } catch (error: unknown) {
55 handleApiError(error, {
56 statusCode: 502,
57 message: ERROR_PROVENANCE_FETCH_FAILED,
58 })
59 }
60 },
61 {
62 maxAge: CACHE_MAX_AGE_ONE_HOUR,
63 swr: true,
64 getKey: event => {
65 const pkg = getRouterParam(event, 'pkg') ?? ''
66 return `provenance:v1:${pkg.replace(/\/+$/, '').trim()}`
67 },
68 },
69)